import Tools;

typedef Bench = { name : String, f : Void -> Dynamic, time : Float };

class Benchs implements haxe.Public {

	public static inline var LOOPS = 5000000;

	var abenchs : Array<Bench>;
	var baseTime : Float;
	var methods : Array<{ name : String, f : Void -> Void }>;

	function new() {
		abenchs = new Array();
		methods = new Array();
		baseTime = 0;
		var fields = Type.getInstanceFields(Benchs);
		for( f in fields )
			if( f.length > 4 && f.substr(0,4) == "time" )
				methods.push({ name : f.substr(4), f : Reflect.field(this,f) });
		methods.sort(function(m1,m2) return m1.name > m2.name ? -1 : 1);
	}

	function time__Loops() {
		time("While Loop",function() {
			var i = 0;
			while( i < LOOPS ) {
				i++;
			}
		});
		time("Empty Loop",function() {
			for( i in 0...LOOPS ) {
				null;
			}
		});
	}

	function timeCalls() {
		time("Void Call",function() {
			var t = new Tools();
			for( i in 0...LOOPS )
				t.empty();
		});
		time("Static call",function() {
			for( i in 0...LOOPS )
				Tools.staticCall();
		});
		time("3params Call",function() {
			var t = new Tools();
			for( i in 0...LOOPS )
				t.f3(i,i,i);
		});
		time("3params Static Call",function() {
			for( i in 0...LOOPS )
				Tools.static3(i,i,i);
		});
		time("Closure Call",function() {
			var f = new Tools().f3;
			for( i in 0...LOOPS )
				f(i,i,i);
		});
		time("Static Closure Call",function() {
			var f = Tools.static3;
			for( i in 0...LOOPS )
				f(i,i,i);
		});
	}

	function timeIO_Int() {
		time("Array Read",function() {
			var x = 0;
			var a50 = new Tools().a50;
			for( n in 0...Std.int(LOOPS/50) ) {
				for( i in 0...50 )
					x = a50[i];
			}
			return x;
		});
		time("Array Write",function() {
			var a50 = new Tools().a50;
			for( n in 0...Std.int(LOOPS/50) ) {
				for( i in 0...50 )
					a50[i] = n;
			}
			return a50;
		});
		#if flash10
		time("Vector Read",function() {
			var x = 0;
			var v50 = new Tools().v50;
			for( n in 0...Std.int(LOOPS/50) ) {
				for( i in 0...50 )
					x = v50[i];
			}
			return x;
		});
		time("Vector Write",function() {
			var v50 = new Tools().v50;
			for( n in 0...Std.int(LOOPS/50) ) {
				for( i in 0...50 )
					v50[i] = n;
			}
			return v50;
		});
		#if !as3
		time("VMem Read Byte",function() {
			var count = Std.int(LOOPS/50);
			var x = 0;
			new Tools().initVMem(count);
			for( n in 0...count )
				for( i in 0...50 )
					x = flash.Memory.getByte(i);
			return x;
		});
		time("VMem Read Int",function() {
			var count = Std.int(LOOPS/50);
			var x = 0;
			new Tools().initVMem(4 * count);
			for( n in 0...count )
				for( i in 0...50 )
					x = flash.Memory.getI32(i << 2);
			return x;
		});
		time("VMem Write Byte",function() {
			var count = Std.int(LOOPS/50);
			new Tools().initVMem(count);
			for( n in 0...count )
				for( i in 0...50 )
					flash.Memory.setByte(i,n);
		});
		time("VMem Write Int",function() {
			var count = Std.int(LOOPS/50);
			new Tools().initVMem(4 * count);
			for( n in 0...count )
				for( i in 0...50 )
					flash.Memory.setI32(i << 2,n);
		});
		#end
		#end
	}

	function timeIO_Float() {
		time("Array Read",function() {
			var x = 0.;
			var a50 = new Tools().a50f;
			for( n in 0...Std.int(LOOPS/50) ) {
				for( i in 0...50 )
					x = a50[i];
			}
			return x;
		});
		time("Array Write",function() {
			var a50 = new Tools().a50f;
			for( n in 0...Std.int(LOOPS/50) ) {
				var k : Float = n;
				for( i in 0...50 )
					a50[i] = k;
			}
			return a50;
		});
		#if flash9
		time("ByteArray Read",function() {
			var b = new flash.utils.ByteArray();
			var x = 0.;
			for( i in 0...50 )
				b.writeDouble(i);
			for( n in 0...Std.int(LOOPS/50) ) {
				b.position = 0;
				for( i in 0...50 )
					x = b.readDouble();
			}
			return x;
		});
		time("ByteArray Write",function() {
			var b = new flash.utils.ByteArray();
			for( n in 0...Std.int(LOOPS/50) ) {
				var k : Float = n;
				b.position = 0;
				for( i in 0...50 )
					b.writeDouble(k);
			}
		});
		#end
		#if flash10
		time("Vector Read",function() {
			var x = 0.;
			var v50 = new Tools().v50f;
			for( n in 0...Std.int(LOOPS/50) ) {
				for( i in 0...50 )
					x = v50[i];
			}
			return x;
		});
		time("Vector Write",function() {
			var v50 = new Tools().v50f;
			for( n in 0...Std.int(LOOPS/50) ) {
				var k : Float = n;
				for( i in 0...50 )
					v50[i] = k;
			}
			return v50;
		});
		#if !as3
		time("VMem Read Float",function() {
			var count = Std.int(LOOPS/50);
			var x = 0.;
			new Tools().initVMem(4 * count);
			for( n in 0...count ) {
				for( i in 0...50 )
					x = flash.Memory.getFloat(i<<2);
			}
			return x;
		});
		time("VMem Read Double",function() {
			var count = Std.int(LOOPS/50);
			var x = 0.;
			new Tools().initVMem(8 * count);
			for( n in 0...count )
				for( i in 0...50 )
					x = flash.Memory.getDouble(i << 3);
			return x;
		});
		time("VMem Write Float",function() {
			var count = Std.int(LOOPS/50);
			new Tools().initVMem(4 * count);
			for( n in 0...count ) {
				var k : Float = n;
				for( i in 0...50 )
					flash.Memory.setFloat(i<<2,k);
			}
		});
		time("VMem Write Double",function() {
			var count = Std.int(LOOPS/50);
			new Tools().initVMem(8 * count);
			for( n in 0...count ) {
				var k : Float = n;
				for( i in 0...50 )
					flash.Memory.setDouble(i << 3,k);
			}
		});
		#end
		#end
	}

	function timeFields() {
		time("Instance Field read",function() {
			var x = 0;
			var t = new Tools();
			for( i in 0...LOOPS )
				x = t.a;
			return x;
		});
		time("Instance Field write",function() {
			var t = new Tools();
			for( i in 0...LOOPS )
				t.a = i;
		});
		time("Static Field read",function() {
			var x = 0;
			for( i in 0...LOOPS )
				x = Tools.staticA;
			return x;
		});
		time("Static Field write",function() {
			for( i in 0...LOOPS )
				Tools.staticA = i;
		});
		time("Anon field read",function() {
			var x = 0;
			var t = { a : 15, b : 541, a50 : new Array() };
			for( i in 0...LOOPS )
				x += t.a;
			return x;
		});
		time("Anon field write",function() {
			var t = { a : 15, b : 541, a50 : new Array() };
			for( i in 0...LOOPS )
				t.a = i;
		});
		#if !as3
		time("Interf field read",function() {
			var x = 0;
			var t : Interf = new Tools();
			for( i in 0...LOOPS )
				x += t.a;
			return x;
		});
		time("Interf field write",function() {
			var t : Interf = new Tools();
			for( i in 0...LOOPS )
				t.a = i;
		});
		#end
		time("Half-anon field read",function() {
			var x = 0;
			var t : { a : Int } = new Tools();
			for( i in 0...LOOPS )
				x += t.a;
			return x;
		});
		time("Half-anon field write",function() {
			var t : { a : Int } = new Tools();
			for( i in 0...LOOPS )
				t.a = i;
		});
	}

	function timeMisc() {
		time("New",function() {
			var x = null;
			for( i in 0...LOOPS )
				x = new Empty();
			return x;
		});
	}

	function timeCalculus() {
		time("Int Add",function() {
			var x = Std.random(1000);
			var y = 0;
			for( i in 0...LOOPS )
				y = x + x;
			return x;
		});
		time("Float Add",function() {
			var x = Math.random();
			var y = 0.;
			for( i in 0...LOOPS )
				y = x + x;
			return x;
		});
		time("Float Int Add",function() {
			var x = Math.random();
			var y = 0.;
			for( i in 0...LOOPS )
				y = x + i;
			return x;
		});
		time("Float to Int",function() {
			var x = Math.random();
			var y = 0;
			for( i in 0...LOOPS )
				y = Std.int(x);
			return y;
		});
		time("Int to Float",function() {
			var x = Std.random(1000);
			var y = 0.;
			for( i in 0...LOOPS )
				y = x;
			return y;
		});
		time("Int Mul",function() {
			var x = Std.random(1000);
			var y = 0;
			for( i in 0...LOOPS )
				y = x * x;
			return x;
		});
		time("Float Mul",function() {
			var x = Math.random();
			var y = 0.;
			for( i in 0...LOOPS )
				y = x * x;
			return x;
		});
		time("Float Div",function() {
			var x = Math.random();
			var y = 0.;
			for( i in 0...LOOPS )
				y = x / x;
			return x;
		});
	}

	function timeMaths() {
		time("Abs",function() {
			var x = 0.;
			var y = Math.random();
			for( i in 0...LOOPS )
				x = Math.abs(y);
			return x;
		});
		time("Sqrt",function() {
			var x = 0.;
			var y = Math.random();
			for( i in 0...LOOPS )
				x = Math.sqrt(y);
			return x;
		});
		time("Cos",function() {
			var x = 0.;
			var y = Math.random();
			for( i in 0...LOOPS )
				x = Math.cos(y);
			return x;
		});
		time("Sin",function() {
			var x = 0.;
			var y = Math.random();
			for( i in 0...LOOPS )
				x = Math.sin(y);
			return x;
		});
		time("Math Cos",function() {
			var x = 0.;
			var y = Math.random();
			var m = Math;
			for( i in 0...LOOPS )
				x = m.cos(y);
			return x;
		});
		time("Math Sin",function() {
			var x = 0.;
			var y = Math.random();
			var m = Math;
			for( i in 0...LOOPS )
				x = m.sin(y);
			return x;
		});
		time("Fast Cos",function() {
			var x = 0.;
			var y = Math.random();
			var m = new FastMath();
			for( i in 0...LOOPS )
				x = m.cos(y);
			return x;
		});
		time("Fast Sin",function() {
			var x = 0.;
			var y = Math.random();
			var m = new FastMath();
			for( i in 0...LOOPS )
				x = m.sin(y);
			return x;
		});
		time("Inline Fast Cos",function() {
			var x = 0.;
			var y = Math.random();
			var m = new FastMath();
			for( i in 0...LOOPS )
				x = m.icos(y);
			return x;
		});
		time("Inline Fast Sin",function() {
			var x = 0.;
			var y = Math.random();
			var m = new FastMath();
			for( i in 0...LOOPS )
				x = m.isin(y);
			return x;
		});
		time("Tan",function() {
			var x = 0.;
			var y = Math.random();
			for( i in 0...LOOPS )
				x = Math.tan(y);
			return x;
		});
		time("Atan",function() {
			var x = 0.;
			var y = Math.random();
			for( i in 0...LOOPS )
				x = Math.atan(y);
			return x;
		});
		time("Atan2",function() {
			var x = 0.;
			var y = Math.random();
			for( i in 0...LOOPS )
				x = Math.atan2(y,y);
			return x;
		});
		time("InvSqrt",function() {
			var x = 0.;
			var y = Math.random();
			for( i in 0...LOOPS )
				x = 1.0 / Math.sqrt(y);
			return x;
		});
		#if (flash10 && !as3)
		time("Math InvSqrt",function() {
			var x = 0.;
			var y = Math.random();
			var m = Math;
			for( i in 0...LOOPS )
				x = 1.0 / m.sqrt(y);
			return x;
		});
		time("Fast InvSqrt",function() {
			var x = 0.;
			var y = Math.random();
			var m = new FastMath();
			m.setupInvSqrt();
			for( i in 0...LOOPS )
				x = m.invSqrt(y);
		});
		#end
	}

	function time(name,f) {
		var b = { name : name, f : f, time : 0. };
		abenchs.push(b);
		return b;
	}

	function format( f : Float ) {
		return Math.ceil(f * 100) / 100;
	}

	function run() {
		var b = abenchs.shift();
		if( b == null ) {
			log("DONE");
			return false;
		}
		if( b.f == null ) {
			log(b.name);
			return true;
		}
		var t = haxe.Timer.stamp();
		var dt;
		var count = 0;
		while( true ) {
			b.f();
			dt = haxe.Timer.stamp() - t;
			count++;
			if( dt > 0.8 )
				break;
		}
		b.time = (haxe.Timer.stamp() - t) * 1000000000.0 / (count * LOOPS) - baseTime;
		if( baseTime == 0 )
			baseTime = b.time;
		log(StringTools.rpad("#"+count," ",5)+" "+b.name + " : "+format(b.time));
		return true;
	}

	function init() {
		for( m in methods ) {
			time("==== "+m.name+" ====",null);
			Reflect.callMethod(this,m.f,[]);
		}
	}

	#if flash
	var tf : flash.text.TextField;
	#end

	function log( s : String ) {
		#if flash
		if( tf == null ) {
			var me = this;
			var mc = flash.Lib.current;
			tf = new flash.text.TextField();
			mc.addChild(tf);
			tf.width = mc.stage.stageWidth - 10;
			tf.height = mc.stage.stageHeight - 10;
			tf.border = true;
			tf.borderColor = 0;
			tf.multiline = true;
			tf.selectable = true;
			mc.stage.addEventListener(flash.events.KeyboardEvent.KEY_DOWN,function(e:flash.events.KeyboardEvent) {
				if( e.keyCode == 27 ) {
					me.log("ABORT");
					me.abenchs = new Array();
				}
			});
		}
		tf.appendText(s + "\n");
		tf.scrollV = Std.int(tf.height);
		#elseif neko
		neko.Lib.println(s);
		#else
		trace(s);
		#end
	}

	static function main() {
		var m = new Benchs();
		m.init();
		#if flash
		m.log( "Player = "+flash.system.Capabilities.version+" VM = "+flash.system.System.vmVersion + (flash.system.Capabilities.isDebugger ? " DEBUG" : "") );
		var mc = flash.Lib.current;
		var run = null;
		run = function(_) if( !m.run() ) mc.removeEventListener(flash.events.Event.ENTER_FRAME,run);
		mc.addEventListener(flash.events.Event.ENTER_FRAME,run);
		#elseif neko
		while( m.run() ) { }
		#end
	}

}